package cn.edu.zzuli.nothinglink.service.impl;

import cn.edu.zzuli.nothinglink.constant.LiveConst;
import cn.edu.zzuli.nothinglink.entity.*;
import cn.edu.zzuli.nothinglink.mapper.LiveInfoMapper;
import cn.edu.zzuli.nothinglink.service.ClassesService;
import cn.edu.zzuli.nothinglink.service.CourseService;
import cn.edu.zzuli.nothinglink.service.LiveInfoService;
import cn.edu.zzuli.nothinglink.service.LiveRecordService;
import cn.edu.zzuli.nothinglink.vo.LiveInitInfoVo;
import cn.edu.zzuli.nothinglink.vo.StartLiveVo;
import cn.edu.zzuli.nothinglink.ws.LiveStartEndPoint;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author suzyyy
 * @since 2021-04-08
 */
@Service
public class LiveInfoServiceImpl extends ServiceImpl<LiveInfoMapper, LiveInfo> implements LiveInfoService {

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    @Autowired
    LiveRecordService liveRecordService;

    @Autowired
    ClassesService classesService;

    @Autowired
    CourseService courseService;

    @Override
    public R createLiveRoom2TheTeaId(Integer teaId) {

        CompletableFuture<Boolean> getIsCreatedTask = CompletableFuture.supplyAsync(() -> {
            LiveInfo liveInfo = getMyLive(teaId);
            if (liveInfo == null) return true;
            return false;
        });

        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        LiveInfo liveInfo = new LiveInfo();
        liveInfo.setLiveBelongTeaId(teaId);
        liveInfo.setLiveUuid(uuid);

        try {
            String push_url_prefix = "rtmp://";
            String pull_url_prefix = "http://";
            //因为使用 rtmp协议，肯定要是.flv结尾的
            String pull_url_suffix = ".flv";
            //以下这些信息，应该放在 yml中进行配置。这里偷懒了
            String app = "live";
            String port = "8080";
            //推流的 stream 就应该是我们的直播间，所以，直播间的uuid = stream名字
            String stream = uuid;
            String ip = "127.0.0.1";
            //生成密钥
            String live_secret = UUID.randomUUID().toString()
                    .substring(0, 8)
                    .replaceAll("-", "");

            //开始拼装
            String push_url = push_url_prefix + ip + ":1935" + "/" + app + "/" + stream;
            String pull_url = pull_url_prefix + ip + ":" + port + "/" + app + "/" + stream + "/" + live_secret + pull_url_suffix;

            liveInfo.setLivePushUrl(push_url).setLivePullUrl(pull_url).setLiveSecret(live_secret);
            if (getIsCreatedTask.get()){
                //保存直播间信息
                save(liveInfo);
                //是否拥有直播间设为 true、
                redisTemplate.opsForValue().set(LiveConst.IS_HAVE_LIVE.KEY() + teaId,  true);
                //是否开启直播设为 false。
                redisTemplate.opsForValue().set(LiveConst.LIVE_IS_START.KEY() + teaId,  false);
                //是否推流设为false
                redisTemplate.opsForValue().set(LiveConst.LIVE_IS_PUSH.KEY() + teaId, false);
                return R.success().add("liveInfo", liveInfo);
            }else {
                //是否拥有直播间设为 true、
                redisTemplate.opsForValue().set(LiveConst.IS_HAVE_LIVE.KEY() + teaId,  true);
                //是否开启直播设为 false。
                redisTemplate.opsForValue().set(LiveConst.LIVE_IS_START.KEY() + teaId,  false);
                //是否推流设为false
                redisTemplate.opsForValue().set(LiveConst.LIVE_IS_PUSH.KEY() + teaId, false);
                return R.error().add("error", "已经有直播间了");
            }
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            System.out.println("出错啦");
        }

        return R.error();
    }

    @Override
    public LiveInitInfoVo getInitInfo(Integer teaId) {
        //这里如果判断 hash结构是否存在的话，还得去查找数据库。所以再设置一个live:start标志位
        LiveInitInfoVo infoVo = new LiveInitInfoVo();

        if (redisTemplate.hasKey(LiveConst.IS_HAVE_LIVE.KEY() + teaId)) {
            infoVo.setLiveHave((Boolean) redisTemplate.opsForValue().get(LiveConst.IS_HAVE_LIVE.KEY() + teaId));

        }else {
            infoVo.setLiveHave(false);
        }

        if (redisTemplate.hasKey(LiveConst.LIVE_IS_START.KEY() + teaId)) {
            infoVo.setStart((Boolean) redisTemplate.opsForValue().get(LiveConst.LIVE_IS_START.KEY() + teaId));
        }else {
            infoVo.setStart(false);
        }

        if (redisTemplate.hasKey(LiveConst.LIVE_IS_PUSH.KEY() + teaId)) {
            infoVo.setPush((Boolean) redisTemplate.opsForValue().get(LiveConst.LIVE_IS_PUSH.KEY() + teaId));
        }else {
            infoVo.setPush(false);
        }
        return infoVo;
    }

    /**
     * 说一下我们开启直播的步骤
     *
     * 首先要有一个直播间（毫无疑问...）
     * 然后配置要直播的课程和班级。
     * 然后点击开始直播后，执行该方法，该方法在redis 中存入了一个hash结构的数据项
     * 如果有该hash，那么证明已开播
     * 此时，我们的老师才可以obs进行推流。否则不允许推流。具体可以在srs_http_callback回调函数中进行验证
     *
     * 推流地址：rtmp://{ip}:{port(rtmp默认断后号为1935)}/live/{老师直播间的uuid}/{直播密钥}
     * 拉流地址: http://{ip}:{port}/live/{老师直播间的uuid}/{直播密钥}.flv
     *
     * 当老师成功推流后，我们可以通过websocket来 通知前端页面更换界面。
     * 此时，才可以发起签到，提出问题，关闭直播。
     *
     * 关闭直播后，删除该hash。
     * @param teaId
     * @param vo
     * @return
     */
    @Override
    public boolean startLiveAndSetLiveInfo(Integer teaId, StartLiveVo vo) {
        try {
            CompletableFuture<List<Classes>> getClassInfoTask = CompletableFuture.supplyAsync(() -> {
                //获取班级信息
                return classesService.listByIds(vo.getClassIds());
            });

            LiveInfo myLive = getMyLive(teaId);
            if (myLive == null) return false;
            //注意这里的真实key 为 live:stream:uuid:{直播间的uuid}
            //在 redis 中有此hash。证明该老师对应的直播间已开播。
            //TODO 当结束直播的时候。注意删除该hash
            //在redis中设置一下自己的配置信息。这里使用 hash
            BoundHashOperations<String, Object, Object> ops = redisTemplate
                    .boundHashOps(LiveConst.LIVE_START_INFO.KEY() + myLive.getLiveUuid());

            HashMap<String, Object> liveInfo = new HashMap<>();
            //省略部分网络带宽，防止连续发请求
            liveInfo.put(LiveConst.LIVE_CLASS_IDS.KEY(), vo.getClassIds());
            liveInfo.put(LiveConst.LIVE_COURSE_ID.KEY(), vo.getCourseId());
            liveInfo.put(LiveConst.LIVE_PUBLISHER.KEY(), teaId);
            liveInfo.put(LiveConst.LIVE_SECRET.KEY(), vo.getLiveSecret());
            liveInfo.put(LiveConst.LIVE_PULL_URL.KEY(), vo.getLivePullUrl());
            liveInfo.put(LiveConst.LIVE_NAME.KEY(), vo.getLiveName());

            liveInfo.put(LiveConst.LIVE_IS_ACCESS_OTHER_CLASS.KEY(), vo.isAccessOtherCLass());


            //修改live:start标志位
            redisTemplate.opsForValue().set(LiveConst.LIVE_IS_START.KEY() + teaId,  true);

            List<Classes> classes = getClassInfoTask.get();

            //添加一条开播记录到的db
            LiveRecord record = new LiveRecord();
            record.setLiveId(myLive.getLiveId())
                    .setLiveName(vo.getLiveName())
                    .setLiveCourseId(vo.getCourseId())
                    .setLiveClassJson(JSON.toJSONString(classes))
                    .setStartTime(LocalDateTime.now());
            liveRecordService.save(record);

            //设置最新的 live record Id.
            redisTemplate.opsForHash().put(LiveConst.LIVE_CURRENT_OR_LAST.KEY(), LiveConst.LIVE_RECORD_ID.KEY(), record.getRecordId());
            redisTemplate.opsForHash().put(LiveConst.LIVE_CURRENT_OR_LAST.KEY(), LiveConst.LIVE_CLASS_IDS.KEY(), vo.getClassIds());
            redisTemplate.opsForHash().put(LiveConst.LIVE_CURRENT_OR_LAST.KEY(), LiveConst.LIVE_COURSE_ID.KEY(), vo.getCourseId());

            liveInfo.put(LiveConst.LIVE_RECORD_ID.KEY(), record.getRecordId());
            //设置总人数。
            liveInfo.put(LiveConst.LIVE_TOTAL_NUMBER.KEY(), classes.stream().mapToInt(Classes::getClassStuNumber).sum());
            ops.putAll(liveInfo);
            return true;
        } catch (Exception e) {
            System.out.println(e);
            return false;
        }

    }

    @Override
    public Integer onConnect(SrsCallBackData callBackData) {
        String stream = getStream(callBackData);
        //如果没有此hash，证明前端还没有开启直播
        if (redisTemplate.hasKey(LiveConst.LIVE_START_INFO.KEY() + stream)) {
            return 0;
        }
        //返回的不是整0,srs视为拒绝连接
        return 1;
    }

    @Override
    public Integer onPublish(SrsCallBackData callBackData) {
        String stream = getStream(callBackData);
        Integer teaId = (Integer) redisTemplate.opsForHash()
                    .get(LiveConst.LIVE_START_INFO.KEY() + stream,
                            LiveConst.LIVE_PUBLISHER.KEY());
        //给前端发送信息，更改标志位
        LiveStartEndPoint.send2UserById(teaId, R.success().add("push", true));
        //是否推流设为true
        redisTemplate.opsForValue().set(LiveConst.LIVE_IS_PUSH.KEY() + teaId, true);
        return 0;
    }

    @Override
    public Integer onClose(SrsCallBackData callBackData) {
        String stream = getStream(callBackData);
        Integer teaId = (Integer) redisTemplate.opsForHash()
                .get(LiveConst.LIVE_START_INFO.KEY() + stream,
                        LiveConst.LIVE_PUBLISHER.KEY());
        //给前端发送信息，更改标志位
        LiveStartEndPoint.send2UserById(teaId, R.success().add("push", false));
        //是否推流设为false
        redisTemplate.opsForValue().set(LiveConst.LIVE_IS_PUSH.KEY() + teaId, false);
        return 0;
    }

    @Override
    public boolean stopLiveAndRemoveKeyFromRedis(String stream) {

        //这里是关闭直播。
        //删除存放在 redis 中的hash数据
        if (redisTemplate.hasKey(LiveConst.LIVE_START_INFO.KEY() + stream)) {
            Integer teaId = (Integer) redisTemplate.opsForHash()
                    .get(LiveConst.LIVE_START_INFO.KEY() + stream,
                            LiveConst.LIVE_PUBLISHER.KEY());
            //先检测是不是还在推流。如果还在推流。提醒老师先去关闭推流
            boolean isPush = (boolean) redisTemplate.opsForValue().get(LiveConst.LIVE_IS_PUSH.KEY() + teaId);
            System.out.println(isPush);
            if (isPush) return false;

            //推流关闭了，那我们可以直接更改直播记录了。
            //是否开始直播写成false。
            redisTemplate.opsForValue().set(LiveConst.LIVE_IS_START.KEY() + teaId, false);

            //更改数据库的结束日期
            Integer recordId = (Integer) redisTemplate.opsForHash()
                    .get(LiveConst.LIVE_START_INFO.KEY() + stream,
                            LiveConst.LIVE_RECORD_ID.KEY());

            liveRecordService.updateById(
                    new LiveRecord().setRecordId(recordId).setEndTime(LocalDateTime.now())
            );

            //然后删除这个hash
            redisTemplate.delete(LiveConst.LIVE_START_INFO.KEY() + stream);

            return true;
        }
        return false;
    }

    @Override
    public R getTheCourseLiveIsStart(Integer courseId, String stream) {
        if (redisTemplate.hasKey(LiveConst.LIVE_START_INFO.KEY() + stream)) {
            //判断课程id是不是一致。
            Integer courseId_redis = (Integer) redisTemplate.opsForHash()
                    .get(LiveConst.LIVE_START_INFO.KEY() + stream,
                    LiveConst.LIVE_COURSE_ID.KEY());

            //获取直播名字
            String name = (String) redisTemplate.opsForHash()
                    .get(LiveConst.LIVE_START_INFO.KEY() + stream,
                            LiveConst.LIVE_NAME.KEY());
            if (courseId_redis == courseId) return R.success().add("liveName", name);
        }
        return R.error().add("error", "该课程还没有开播，有可能该老师在直播其他课程哦 :) !");
    }

    private String getStream(SrsCallBackData callBackData) {
        String[] split = callBackData.getApp().split("/");
        String stream = split[split.length - 1];
        return stream;
    }

    private LiveInfo getMyLive(Integer teaId) {
        return getOne(
                new LambdaQueryWrapper<LiveInfo>().eq(
                        LiveInfo::getLiveBelongTeaId, teaId
                ));
    }

}
